分頁(pagination)功能,就算在資料量較少的小型專案,也具有相當的重要性。
沒分頁,API 照樣能運作——只是效能會受到影響,特別是在資料量大的情況下。
當 API 一次回傳大量資料時,不僅會增加伺服器負擔,還可能導致客戶端處理緩慢,甚至出現超時或記憶體不足等問題。
透過分頁,我們可以避免一次性傳輸大量資料,提高 API 的效能,同時提升使用者的體驗。
這個主題將分為上、下兩篇,介紹如何在 Django Ninja 中實作分頁功能——從內建的分頁器到自定義分頁類別,以滿足不同需求。
本文所有的程式碼變動,可參考這個 PR。
分頁的核心作用是將大量資料拆分成小部分傳輸,每次只給一些,從而避免效能問題。
具體而言,分頁能夠幫助我們:
因此,無論專案規模大小,實作高效的分頁策略,對 API 的擴充性與使用者體驗,都有明顯幫助。
了解了分頁的重要性後,我們來開始實作吧!
這次的範例 API 是——「取得文章列表」。
因為專案的資料庫中已經有超過 60 筆文章資料,非常適合演示。
什麼?你說你沒有?歡迎參考〈卷 12:請求(四)Request Body 與 Schema 介紹〉結尾的「中場休息與準備」段落。
本篇我們將使用 Django Ninja 內建的PageNumberPagination
分頁器,來實作簡單、有效的分頁功能。自定義部分,則留在下一篇。
在 Django Ninja 中,分頁功能可以透過內建的paginate
裝飾器加上分頁器(即分頁類別)實現。
Django Ninja 提供了兩個內建分頁器,為方便你理解,以下是它們的白話文介紹:
LimitOffsetPagination
:根據「從哪一筆資料開始」和「要抓多少筆資料」來進行分頁,適合資料量很大時使用。例如:「從第 20 筆開始,抓 10 筆資料」。PageNumberPagination
:透過頁碼進行分頁,用戶只需要指定想要的頁數,例如「抓第 2 頁的資料」。每頁的資料數量可以由開發者自行設定。我個人偏好使用PageNumberPagination
,或自定義一個類似的版本(也就是下一篇的內容)。
但預設的分頁器是LimitOffsetPagination
,所以在使用paginate
裝飾器時,需要你主動聲明第一參數。等一下你就知道了。
在那之前,我們先來回顧「取得文章列表」的 API 現狀。
如下所示,由於尚未實作分頁功能,它會一次性回傳所有文章資料:
@router.get('/posts/', response=list[PostListResponse], ...)
def get_posts(
request: HttpRequest,
title: None | str = Query(None, min_length=2, max_length=10),
) -> QuerySet[Post]:
"""
取得文章列表
"""
posts = Post.objects.all()
if title:
posts = posts.filter(
title__icontains=title).select_related('author')
return posts
這在文章少的時候可以運作如常,但隨著資料量增加,效能將會受到影響。
接下來,我們要透過 Django Ninja 內建的分頁器來改善這個問題。
使用內建的PageNumberPagination
,我們可以輕鬆為 API 加上分頁功能。
只要在 view 函式上使用@paginate
裝飾器並加入參數,如下:
from ninja.pagination import PageNumberPagination, paginate
...
@router.get(...)
@paginate(PageNumberPagination, page_size=10) # 分頁實作
def get_posts(...) -> QuerySet[Post]:
"""
取得文章列表
"""
...
我省略了大部分內容,這裡只需要關注分頁的實作。
如前所述,第一參數PageNumberPagination
必須由你主動聲明——如果是用LimitOffsetPagination
則不必。
實作的效果是,每頁改為顯示 10 筆文章,這數量可以透過page_size
參數控制。
咦,那換頁怎麼辦?我們看一下PageNumberPagination
的原始碼:
class PageNumberPagination(AsyncPaginationBase):
class Input(Schema):
page: int = Field(1, ge=1)
...
Input
代表請求的查詢參數(下一篇會細論),換句話說,你可以在 URL 的查詢參數(query parameters)中使用參數page
來指定「頁碼」,以達到換頁效果。
呼叫 API,並使用?page=2
作為查詢參數,看看效果如何:
符合預期!
回應中顯示的確實是第 2 頁內容——文章 id 從 11 開始,總共 10 筆。
透過 Django Ninja 內建的分頁器,我們能夠迅速為 API 加入分頁功能,立即實現簡單的分頁需求。
然而,內建分頁器在控制靈活性上有所不足,也無法建立客製化回應。當分頁需求變得複雜時,就顯得有點捉襟見肘。
此時,自定義分頁器會是一個更好的解決方案。
下一篇,我們將探討如何在 Django Ninja 中自定義分頁類別,以滿足這些進階需求。
本文同步發表於我的部落格——Code and Me